home *** CD-ROM | disk | FTP | other *** search
/ The Fatted Calf / The Fatted Calf.iso / Applications / Graphics / Gnuplot / Source / Gnuplot.m < prev    next >
Encoding:
Text File  |  1993-03-18  |  13.6 KB  |  639 lines

  1.  
  2. static char RCSId[]="$Id: Gnuplot.m,v 1.1.1.1 1993/03/18 03:33:26 davis Exp $";
  3.  
  4.  
  5. #import <appkit/Application.h>
  6. #import <appkit/Listener.h>
  7. #import <appkit/Matrix.h>
  8. #import <appkit/Menu.h>
  9. #import <appkit/MenuCell.h>
  10. #import <appkit/OpenPanel.h>
  11. #import <appkit/PrintInfo.h>
  12. #import <appkit/Speaker.h>
  13.  
  14. #import <appkit/publicWraps.h>        /* NXConvertWinNumToGlobal()    */
  15.  
  16. #import <objc/List.h>
  17. #import <objc/NXStringTable.h>
  18.  
  19. #import <libc.h>            /* MAXPATHLEN            */
  20. #import <strings.h>            /* strcmp()            */
  21.  
  22. #import "Gnuplot.h"
  23. #import "GnuplotPlot.h"
  24. #import "Inspector.h"
  25. #import "Preferences.h"
  26.  
  27.  
  28.  
  29.  
  30. /*
  31.  *  This function is from the NeXTSTEP Developer Example Draw by Paul 
  32.  *  Hegarty.
  33.  *  
  34.  *  Sets the updateAction for every menu item that sends its action to 
  35.  *  the First Responder (i.e. every menu item whose target is nil).  
  36.  *  When autoupdate is on (which is always in this app), each event 
  37.  *  will be followed by an update of every visible menu item.  This 
  38.  *  keeps all unavailable menu items dimmed and disabled so that the 
  39.  *  user knows what options are available at any given time.
  40.  */ 
  41. static void initMenu (Menu* menu)
  42. {
  43.     int count;
  44.     Matrix *matrix;
  45.     MenuCell *cell;
  46.     id matrixTarget, cellTarget;
  47.  
  48.     matrix = [menu itemList];
  49.     matrixTarget = [matrix target];
  50.  
  51.     count = [matrix cellCount];
  52.     while (count--) {
  53.  
  54.         cell = [matrix cellAt:count :0];
  55.         cellTarget = [cell target];
  56.  
  57.         if (!matrixTarget && !cellTarget)
  58.             [cell setUpdateAction:@selector(menuItemUpdate:) forMenu:menu];
  59.         else if ([cell hasSubmenu])
  60.             initMenu(cellTarget);
  61.  
  62.     }
  63. }
  64.  
  65.  
  66.  
  67.  
  68. @implementation Gnuplot
  69.  
  70. + setConstantUpdate:(int)updateType
  71. {
  72.     return [GnuplotPlot setConstantUpdate:updateType];
  73. }
  74.  
  75.  
  76. + setHalvePlot:(BOOL)condition
  77. {
  78.     return [GnuplotPlot setHalvePlot:condition];
  79. }
  80.  
  81.  
  82. + setAddDataToCurrentEnabled:(BOOL) aBool
  83. {
  84.     NXSetServicesMenuItemEnabled("Gnuplot/Add Data to Current Plot", aBool);
  85.     return self;
  86. }
  87.  
  88.  
  89. + setAddDataToNewEnabled:(BOOL) aBool
  90. {
  91.     NXSetServicesMenuItemEnabled("Gnuplot/Plot Data in New Plot", aBool);
  92.     return self;
  93. }
  94.  
  95.  
  96.  
  97. - init
  98. {
  99.     [super init];
  100.  
  101.     zone = [self zone];
  102.  
  103.     docList = [[List allocFromZone: zone] init];
  104.     numUntitled = 1;
  105.     isPoweringOff = NO;
  106.  
  107.     /* Set printinfo margins to 1/2 inch (36 points) on each side */
  108.     [[NXApp printInfo] setMarginLeft:36.0 right:36.0 top:36.0 bottom:36.0];
  109.  
  110.     [NXApp setAutoupdate:YES];
  111.  
  112.     preferences = [[Preferences allocFromZone: zone] init];
  113.     inspector = [[Inspector allocFromZone: zone] init];
  114.  
  115.     return self;
  116. }
  117.  
  118.  
  119.  
  120. - stringSet
  121. {
  122.     return stringSet;
  123. }
  124.  
  125.  
  126. - showInfoPanel:sender
  127. {
  128.     /* Updated automatically by RCS    */
  129.     static char *rev = NULL;
  130.  
  131.     if (!rev) {
  132.     rev = NXCopyStringBufferFromZone ("$Revision: 1.1.1.1 $", zone);
  133.     *(rindex (rev, '$')) = '\0';
  134.     }
  135.  
  136.     if (!infoPanel)
  137.     [NXApp loadNibSection: "InfoPanel.nib" 
  138.                         owner: self
  139.                     withNames: NO
  140.                  fromZone: zone];
  141.  
  142.     [revisionTextField setStringValue: rev + 11];
  143.     [infoPanel makeKeyAndOrderFront:self];
  144.  
  145.     return self;
  146. }
  147.  
  148.  
  149.  
  150. - showCopyingPanel:sender
  151. {
  152.     if (!copyingPanel)
  153.     [NXApp loadNibSection: "CopyingPanel.nib"
  154.                         owner: self
  155.                     withNames: NO
  156.                      fromZone: zone];
  157.  
  158.     [copyingPanel makeKeyAndOrderFront:self];
  159.  
  160.     return self;
  161. }
  162.  
  163.  
  164.  
  165. - showInspectorPane:sender
  166. {
  167.     /* Inspector was created in -init */
  168.  
  169.     [inspector selectPane:[sender selectedTag]];
  170.     [[inspector window] makeKeyAndOrderFront:self];
  171.     return self;
  172. }
  173.  
  174.  
  175.  
  176. - showPreferencesPanel:sender
  177. {
  178.     /* Preferences was created in -init */
  179.  
  180.     [preferences showPanel:self];
  181.     return self;
  182. }
  183.  
  184.  
  185.  
  186. - new:sender
  187. {
  188.     id newDoc = nil;
  189.  
  190.     if (newDoc = [[GnuplotPlot allocFromZone: zone] init])  {
  191.     numUntitled++;
  192.     [docList addObject:newDoc];
  193.     }
  194.  
  195.     return newDoc;
  196. }
  197.  
  198.  
  199.  
  200. - open:sender
  201. {
  202.     const char *const *files;
  203.     static const char *const fileType[2] = {DOCUMENT_TYPE, NULL};
  204.     OpenPanel *openPanel;
  205.     id newDoc;
  206.     char fullName[MAXPATHLEN];
  207.  
  208.     openPanel = [[OpenPanel new] allowMultipleFiles:YES];
  209.  
  210.     /* Get a list of files to open from the user. */
  211.     if ([openPanel runModalForTypes:fileType]) {
  212.  
  213.     /* For each pathname selected... */
  214.     for (files = [openPanel filenames]; files && *files; files++) {
  215.  
  216.         sprintf (fullName, "%s/%s", [openPanel directory], *files);
  217.  
  218.         /* 
  219.          *  ... check to see if the doc is already opened.  If it 
  220.          *  is, bring it to the front.
  221.          */
  222.         if (newDoc = [self isDocOpen: fullName])
  223.         [[newDoc window] makeKeyAndOrderFront:self];
  224.  
  225.         /* If not, try to create a new one. */
  226.         else if ((newDoc = [[GnuplotPlot allocFromZone: zone]
  227.                           initFromFile: fullName]))
  228.         [docList addObject: newDoc];
  229.  
  230.  
  231.         /* 
  232.          *  Otherwise, just return.  GnuplotPlot will report the 
  233.          *  nature of the error to the user so we don't need to 
  234.          *  here.
  235.          */
  236.         else
  237.         return nil;
  238.  
  239.     }
  240.  
  241.     }
  242.  
  243.     return self;
  244. }
  245.  
  246.  
  247.  
  248.  
  249. - saveAll:sender
  250. {
  251.     [docList makeObjectsPerform:@selector(save:) with:self];
  252.     return self;
  253. }
  254.  
  255.  
  256.  
  257. /* 
  258.  *  This method should update any panels external to the doc which 
  259.  *  reflect the current doc (e.g. the Inspector panel).
  260.  */
  261. - updateApp
  262. {
  263.     [inspector update];
  264.     return self;
  265. }
  266.  
  267.  
  268.  
  269.  
  270. /*  
  271.  *  The doc should send this when it closes so that we can remove it 
  272.  *  from our doc list.
  273.  */
  274. - docDidClose:sender
  275. {
  276.     [docList removeObject:sender];
  277.     [self setCurrentDoc:nil];
  278.     return self;
  279. }
  280.  
  281.  
  282.  
  283. /*  
  284.  *  Checks the doc list to see if the doc specified by fullPath is 
  285.  *  currently open.  If it is, the doc is returned.  Otherwise, nil is 
  286.  *  returned.
  287.  */
  288. - isDocOpen: (const char *) fullPath
  289. {
  290.     int counter, docCount = [docList count];
  291.     id aDoc;
  292.  
  293.     for (counter = 0 ; counter < docCount ; counter++)
  294.     if (!strcmp ([aDoc = [docList objectAt: counter] name], fullPath))
  295.         return aDoc;
  296.  
  297.     return nil;
  298. }
  299.  
  300.  
  301.  
  302.  
  303. - (int)numberNew
  304. {
  305.     return numUntitled;
  306. }
  307.  
  308.  
  309.  
  310. - setCurrentDoc:aDoc
  311. {
  312.     if (aDoc)
  313.     currentDoc = aDoc;
  314.     else
  315.     currentDoc = [[NXApp mainWindow] delegate];
  316.  
  317.     return self;
  318. }
  319.  
  320.  
  321.  
  322.  
  323. /*** Auto-Update Methods ***/
  324.  
  325.  
  326.  
  327. /*
  328.  *  This method is from the NeXTSTEP example Draw by Paul Hegarty.
  329.  *
  330.  *  When a menu is updated, this method is sent by each menu item that 
  331.  *  sends its action to the First Responder.  If the object that would 
  332.  *  respond to an action sent down the Responder chain also responds 
  333.  *  to the validateCommand:, then we send validateCommand: to 
  334.  *  determine whether the menu action is valid now.  Otherwise, if 
  335.  *  there is a Responder to the message, then we assume that the item 
  336.  *  is valid.  The method returns YES if the cell has changed its 
  337.  *  appearance (so that the caller Menu knows to redraw it).
  338.  */
  339. - (BOOL)menuItemUpdate:menuCell
  340. {
  341.     SEL action;
  342.     id responder, target;
  343.     BOOL enable;
  344.  
  345.     target = [menuCell target];
  346.     enable = [menuCell isEnabled];
  347.  
  348.     if (!target) {
  349.     action = [menuCell action];
  350.     responder = [NXApp calcTargetForAction:action];
  351.     if ([responder respondsTo:@selector(validateCommand:)])
  352.         enable = [responder validateCommand:menuCell];
  353.     else
  354.         enable = responder ? YES : NO;
  355.  
  356.     }
  357.  
  358.     if ([menuCell isEnabled] != enable) {
  359.     [menuCell setEnabled:enable];
  360.     return YES;
  361.     } else
  362.         return NO;
  363.  
  364. }
  365.  
  366.  
  367. /*  
  368.  *  This method is sent down the Responder chain by menuItemUpdate:
  369.  *  
  370.  *  Every object that responds to messages sent down the Responder 
  371.  *  chain by menu cells but occasionally can't do perform the action 
  372.  *  should implement validateCommand: and return a boolean value 
  373.  *  telling whether the menu item should be enabled or not.
  374.  *  
  375.  *  For example:  we can respond to saveAll:, but we can't save all if 
  376.  *  there are no docs that need saving.  So we need to implement 
  377.  *  validateCommand: to  disable the saveAll: menu cell when it is 
  378.  *  irrelevant.
  379.  */
  380. - (BOOL)validateCommand:menuCell
  381. {
  382.     SEL action = [menuCell action];
  383.     int count;
  384.  
  385.     if (action == @selector(saveAll:))  {
  386.  
  387.     if ([docList count])
  388.         for (count = [docList count] - 1; count >= 0 ; count--)
  389.         if ([[docList objectAt:count] validateCommand:menuCell])
  390.             return YES;
  391.         
  392.     return NO;
  393.  
  394.     }
  395.  
  396.     return YES;
  397. }
  398.  
  399.  
  400.  
  401.  
  402.  
  403.  
  404.  
  405. /*** Application Delegate Methods ***/
  406.  
  407.  
  408. /* Sent when user opens one of our docs from the Workspace. */
  409.  
  410. - (int)app:sender openFile:(const char *)path type:(const char *)type
  411. {
  412.     id newObject;
  413.  
  414.     /* If the document is of the right type... */
  415.     if (type && !strcmp (type, DOCUMENT_TYPE)) {
  416.  
  417.     /*  
  418.      *  ... first check to see if it's already open.  If it is, 
  419.      *  bring it to the front.
  420.      */
  421.     if (newObject = [self isDocOpen: path])
  422.         [[newObject window] makeKeyAndOrderFront:self];
  423.  
  424.  
  425.     /* Otherwise, attempt to open the document. */
  426.     else if (newObject = [[GnuplotPlot allocFromZone: zone]
  427.                                 initFromFile:path]) {
  428.  
  429.         [docList addObject:newObject];
  430.  
  431.         /*  
  432.          *  Set the OpenPanel directory so that when the user opens 
  433.          *  another doc, the open panel will start where he/she left
  434.          *  off.
  435.          */
  436.         [[OpenPanel new] setDirectory:path];
  437.  
  438.         /* 
  439.          *  This causes documents opened from the Workspace to 
  440.          *  open more slowly, but if we don't the Inspector will 
  441.          *  be outdated until the next event.
  442.          */
  443.         [self updateApp];
  444.         return YES;
  445.  
  446.     }
  447.  
  448.     }
  449.  
  450.     return NO;
  451. }
  452.  
  453.  
  454. - app:sender powerOffIn:(int)ms andSave:(int)aFlag
  455. {
  456.     isPoweringOff = YES;
  457.     return [self appWillTerminate:self];
  458. }
  459.  
  460.  
  461.  
  462. - (BOOL)appAcceptsAnotherFile:sender
  463. {
  464.     /*  
  465.      *  We return YES to this because we can open any number of 
  466.      *  documents.
  467.      */
  468.     return YES;
  469. }
  470.  
  471.  
  472. - appDidInit:sender
  473. {
  474.     initMenu([NXApp mainMenu]);        /* Setup auto-menu-update    */
  475.  
  476.     /*  
  477.      *  Set services delegate to be self so this app can be a 
  478.      *  service-provider and this object can handle the messages.
  479.      */
  480.     [[NXApp appListener] setServicesDelegate:self];
  481.  
  482.  
  483.     /* 
  484.      *  Open a new, untitled document, unless the app was started when 
  485.      *  the user opened a document from the Workspace or the user has 
  486.      *  specified not to with Preferences.
  487.      */
  488.     if (![docList count] && [preferences newDocument])
  489.     [self new:self];
  490.  
  491.     /* 
  492.      *  We make the inspector the key window down here, last, so that 
  493.      *  it will be key when the user begins using the program.  The 
  494.      *  user can begin working without necessarily needing to use the 
  495.      *  mouse.  We also select the user's prefered pane.
  496.      */
  497.     [inspector selectPane: [preferences defaultInspector]];
  498.     [[inspector window] makeKeyAndOrderFront:self];
  499.  
  500.     return self;
  501. }
  502.  
  503.  
  504.  
  505. - appWillTerminate: sender
  506. {
  507.     int editedCount, counter;
  508.     id editedDocs = [[List allocFromZone: zone] init];
  509.     id aDoc;
  510.  
  511.     do {
  512.  
  513.     /*  Build a list of edited, unsaved docs. */
  514.  
  515.     [editedDocs empty];
  516.     counter = [docList count];
  517.     while (--counter >= 0) {
  518.         aDoc = [docList objectAt: counter];
  519.         if ([aDoc isDocEdited])
  520.         [editedDocs addObject: aDoc];
  521.     }
  522.  
  523.     /*  
  524.      *  If there are edited, unsaved docs, ask if the user wants to 
  525.      *  review them, quit, or cancel the quit action (unless this 
  526.      *  method was sent by us during a poweroff/logoff, in which 
  527.      *  case we don't allow a cancel).
  528.      */
  529.     if ((editedCount = [editedDocs count]) > 0)  {
  530.         switch (NXRunAlertPanel ([stringSet valueForStringKey: "quitT"],
  531.                   [stringSet valueForStringKey: "unsavedDocs"],
  532.                   [stringSet valueForStringKey: "reviewB"],
  533.                   [stringSet valueForStringKey: "quitB"],
  534.           isPoweringOff? NULL: [stringSet valueForStringKey: "cancelB"])) {
  535.  
  536.         case NX_ALERTDEFAULT:        /* Review Unsaved    */
  537.  
  538.         counter = editedCount;
  539.         while (--counter >= 0)
  540.             [[editedDocs objectAt: counter] close:self];
  541.         break;
  542.  
  543.         case NX_ALERTALTERNATE:        /* Quit Anyway        */
  544.         editedCount = 0;
  545.         break;
  546.  
  547.         case NX_ALERTOTHER:            /* Cancel        */
  548.         [editedDocs free];
  549.         return nil;
  550.         break;
  551.         }
  552.     }
  553.  
  554.     /*  
  555.      *  If the user chooses to review unsaved, then is shown a Save 
  556.      *  panel for a doc and chooses "Cancel," we end up down here, 
  557.      *  where we'll repeat if there are more unedited docs.  
  558.      *  Basically, we force the user either to save all the docs or 
  559.      *  make a decision in the quit panel to review, quit, or 
  560.      *  cancel.
  561.      */
  562.     
  563.     } while (editedCount > 0);
  564.  
  565.     [editedDocs free];
  566.  
  567.  
  568.     /*
  569.      *  Quit the application.  (We free things here because -free is 
  570.      *  never called -- NXApp simply exit()'s -- but GnuplotPlot's 
  571.      *  implementation of -free deletes the plot's tmp file, so we 
  572.      *  must call it to avoid leaving junk in /tmp.) 
  573.      */
  574.     
  575.     [docList makeObjectsPerform: @selector(free)];
  576.     [docList free];
  577.  
  578.     return self;
  579. }
  580.  
  581.  
  582.  
  583. /*  
  584.  *  This method is sent when a user chooses one of Gnuplot's services 
  585.  *  from the Services menu in another application.
  586.  */
  587. - plotData:pb userData:(const char *)userData error:(char **)errorMessage
  588. {
  589.     char *data;
  590.     int length, i;
  591.     const char *const *types;
  592.     BOOL hasFilename;
  593.  
  594.     types = [pb types];
  595.     hasFilename = NO;
  596.     for (i=0 ; !hasFilename && types[i] ; i++)
  597.     if (!strcmp (types[i], NXFilenamePboardType)) hasFilename = YES;
  598.     if (hasFilename) {
  599.         [pb readType:NXFilenamePboardType data:&data length:&length];
  600.  
  601.     if (data && length) {
  602.         char *path;
  603.  
  604.         path = (char *) malloc (length + 1);
  605.         strncpy (path, data, length);
  606.         path[length] = '\0';
  607.  
  608.         /*  
  609.          *  If there is a current document, we add the data file 
  610.          *  specified on the pasteboard to it.  If there's no 
  611.          *  current document, or if the userData contains "new" 
  612.          *  we create a new document and add the data file to 
  613.          *  that.
  614.          */
  615.  
  616.         if (currentDoc && strcmp (userData, "new"))
  617.         [currentDoc addDataFile:path];
  618.         else
  619.         [[self new:self] addDataFile:path];
  620.  
  621.         [self updateApp];
  622.  
  623.     }
  624.     }
  625.  
  626.     return self;
  627. }
  628.  
  629.  
  630. // Shuts up the compiler about unused RCSId
  631. - (const char *) rcsid
  632. {
  633.     return RCSId;
  634. }
  635.  
  636.  
  637. @end
  638.  
  639.